/*
 * MapRenderer.cpp
 *
 *  Created on: 26 juin 2011
 *      Author: Pierrick Caillon <pierrick@megami.fr>
 */

#include "MapRenderer.h"
#include <gd.h>
#include <gd_io.h>
#include <TagShort.h>
#include <TagByteArray.h>
//#include <stdint.h>
#include <iomanip>
#include <sstream>
#include <stdlib.h>
#include <string.h>

const char
		* const MapRenderer::palette =
				"#00000000,#FF010101,#FF020202,#FF030303,#FF4E6D22,#FF5F862A,#FF6F9C31,#FF070707,#FF988F65,#FFBAB07A,#FFD8CC8E,#FF0B0B0B,#FF0C0C0C,#FF0D0D0D,#FF0E0E0E,#FF0F0F0F,#FF9D0000,#FFC00000,#FF121212,#FF131313,#FF62629D,#FF7979C0,#FF8C8CDF,#FF171717,#FF181818,#FF191919,#FF1A1A1A,#FF1B1B1B,#FF004C00,#FF005D00,#FF006C00,#FF1F1F1F,#FF9D9D9D,#FFC0C0C0,#FFDFDFDF,#FF232323,#FF242424,#FF252525,#FF262626,#FF272727,#FF282828,#FF895023,#FFA05D29,#FF2B2B2B,#FF454545,#FF545454,#FF626262,#FF2F2F2F,#FF27279D,#FF3030C0,#FF3838DF,#FF333333,#FF40331F,#FF353535,#FF5B492C,#FF373737,#FF383838,#FF393939,#FF3A3A3A,#FF3B3B3B,#FF3C3C3C,#FF3D3D3D,#FF3E3E3E,#FF3F3F3F,#FF404040,#FF414141,#FF424242,#FF434343,#FF444444,#FF454545,#FF464646,#FF474747,#FF484848,#FF494949,#FF4A4A4A,#FF4B4B4B,#FF4C4C4C,#FF4D4D4D,#FF4E4E4E,#FF4F4F4F,#FF505050,#FF515151,#FF525252,#FF535353,#FF545454,#FF555555,#FF565656,#FF575757,#FF585858,#FF595959,#FF5A5A5A,#FF5B5B5B,#FF5C5C5C,#FF5D5D5D,#FF5E5E5E,#FF5F5F5F,#FF606060,#FF616161,#FF626262,#FF636363,#FF646464,#FF656565,#FF666666,#FF676767,#FF686868,#FF696969,#FF6A6A6A,#FF6B6B6B,#FF6C6C6C,#FF6D6D6D,#FF6E6E6E,#FF6F6F6F,#FF707070,#FF717171,#FF727272,#FF737373,#FF747474,#FF757575,#FF767676,#FF777777,#FF787878,#FF797979,#FF7A7A7A,#FF7B7B7B,#FF7C7C7C,#FF7D7D7D,#FF7E7E7E,#FF7F7F7F,#FF808080,#FF818181,#FF828282,#FF838383,#FF848484,#FF858585,#FF868686,#FF878787,#FF888888,#FF898989,#FF8A8A8A,#FF8B8B8B,#FF8C8C8C,#FF8D8D8D,#FF8E8E8E,#FF8F8F8F,#FF909090,#FF919191,#FF929292,#FF939393,#FF949494,#FF959595,#FF969696,#FF979797,#FF989898,#FF999999,#FF9A9A9A,#FF9B9B9B,#FF9C9C9C,#FF9D9D9D,#FF9E9E9E,#FF9F9F9F,#FFA0A0A0,#FFA1A1A1,#FFA2A2A2,#FFA3A3A3,#FFA4A4A4,#FFA5A5A5,#FFA6A6A6,#FFA7A7A7,#FFA8A8A8,#FFA9A9A9,#FFAAAAAA,#FFABABAB,#FFACACAC,#FFADADAD,#FFAEAEAE,#FFAFAFAF,#FFB0B0B0,#FFB1B1B1,#FFB2B2B2,#FFB3B3B3,#FFB4B4B4,#FFB5B5B5,#FFB6B6B6,#FFB7B7B7,#FFB8B8B8,#FFB9B9B9,#FFBABABA,#FFBBBBBB,#FFBCBCBC,#FFBDBDBD,#FFBEBEBE,#FFBFBFBF,#FFC0C0C0,#FFC1C1C1,#FFC2C2C2,#FFC3C3C3,#FFC4C4C4,#FFC5C5C5,#FFC6C6C6,#FFC7C7C7,#FFC8C8C8,#FFC9C9C9,#FFCACACA,#FFCBCBCB,#FFCCCCCC,#FFCDCDCD,#FFCECECE,#FFCFCFCF,#FFD0D0D0,#FFD1D1D1,#FFD2D2D2,#FFD3D3D3,#FFD4D4D4,#FFD5D5D5,#FFD6D6D6,#FFD7D7D7,#FFD8D8D8,#FFD9D9D9,#FFDADADA,#FFDBDBDB,#FFDCDCDC,#FFDDDDDD,#FFDEDEDE,#FFDFDFDF,#FFE0E0E0,#FFE1E1E1,#FFE2E2E2,#FFE3E3E3,#FFE4E4E4,#FFE5E5E5,#FFE6E6E6,#FFE7E7E7,#FFE8E8E8,#FFE9E9E9,#FFEAEAEA,#FFEBEBEB,#FFECECEC,#FFEDEDED,#FFEEEEEE,#FFEFEFEF,#FFF0F0F0,#FFF1F1F1,#FFF2F2F2,#FFF3F3F3,#FFF4F4F4,#FFF5F5F5,#FFF6F6F6,#FFF7F7F7,#FFF8F8F8,#FFF9F9F9,#FFFAFAFA,#FFFBFBFB,#FFFCFCFC,#FFFDFDFD,#FFFEFEFE,#FFFFFFFF";

gdImagePtr MapRenderer_InitImage(NamedBinaryTag::DataFile &data);
void MapRenderer_FillImage(gdImagePtr image, NamedBinaryTag::DataFile &data,
		int *palette);
void MapRenderer_DumpImage(gdImagePtr image, std::ostream &output);
int* MapRenderer_CreatePalette(gdImagePtr image, const char* const colors);
void MapRenderer_DeletePalette(int* palette);

class ImageStreamOutput: public gdIOCtx
{
private:
	std::ostream &out;
	static int GetC(struct gdIOCtx *io);
	static int GetBuf(struct gdIOCtx *op, void *buf, int len);
	static void PutC(struct gdIOCtx *io, int val);
	static int PutBuf(struct gdIOCtx *io, const void *buf, int len);
	/* seek must return 1 on SUCCESS, 0 on FAILURE. Unlike fseek! */
	static int Seek(struct gdIOCtx *io, const int offset);
	static long Tell(struct gdIOCtx *io);
	static void Gd_free(struct gdIOCtx *io);
public:
	ImageStreamOutput(std::ostream &out);
	~ImageStreamOutput();
};

MapRenderer::MapRenderer()
{
}

MapRenderer::~MapRenderer()
{
}

std::string MapRenderer::Render(NamedBinaryTag::DataFile & source)
{
	gdImagePtr image = MapRenderer_InitImage(source);
	if (image == NULL)
	{
		std::cerr
				<< __N("The source file does not seems to be a Minecraft map file.\n");
		return std::string();
	}
	int *palette = MapRenderer_CreatePalette(image, MapRenderer::palette);
	MapRenderer_FillImage(image, source, palette);
	MapRenderer_DeletePalette(palette);
	std::ostringstream output;
	MapRenderer_DumpImage(image, output);
	gdImageDestroy(image);
	return output.str();
}

gdImagePtr MapRenderer_InitImage(NamedBinaryTag::DataFile & data)
{
	NamedBinaryTag::TagCompound
			*dataTag =
					dynamic_cast<NamedBinaryTag::TagCompound*> (data.get_RootTag()->get_Item(
							"data"));
	if (dataTag == NULL)
		return NULL;
	NamedBinaryTag::TagShort *widthTag =
			dynamic_cast<NamedBinaryTag::TagShort*> ((*dataTag)["width"]);
	if (widthTag == NULL)
		return NULL;
	NamedBinaryTag::TagShort *heightTag =
			dynamic_cast<NamedBinaryTag::TagShort*> ((*dataTag)["height"]);
	if (heightTag == NULL)
		return NULL;
	gdImagePtr image = gdImageCreateTrueColor(widthTag->get_Value(),
			heightTag->get_Value());
	gdImageAlphaBlending(image, false);
	gdImageSaveAlpha(image, true);
	return image;
}

void MapRenderer_DeletePalette(int *palette)
{
	delete[] palette;
}

void MapRenderer_FillImage(gdImagePtr image, NamedBinaryTag::DataFile & data,
		int *palette)
{
	NamedBinaryTag::TagCompound
			*dataTag =
					dynamic_cast<NamedBinaryTag::TagCompound*> (data.get_RootTag()->get_Item(
							"data"));
	NamedBinaryTag::TagByteArray *colorsTag =
			dynamic_cast<NamedBinaryTag::TagByteArray*> ((*dataTag)["colors"]);
	unsigned char* imageData = (unsigned char*) colorsTag->get_Value();
	int width = gdImageSX(image);
	int height = gdImageSY(image);
	for (int y = 0; y < height; y++)
	{
		int offset = y * width;
		for (int x = 0; x < width; x++)
		{
			volatile int index = (int) imageData[offset + x];
			volatile int color = palette[index];
			gdImageSetPixel(image, x, y, color);
		}
	}
}

int *MapRenderer_CreatePalette(gdImagePtr image, const char* const colors)
{
	int *palette = new int[256];
	char sa[3], sr[3], sg[3], sb[3];
	sa[2] = sr[2] = sg[2] = sb[2] = 0;
	for (int i = 0; i < 256; i++)
	{
		const char *start = colors + 1 + i * 10;
		strncpy(sa, start, 2);
		strncpy(sr, start + 2, 2);
		strncpy(sg, start + 4, 2);
		strncpy(sb, start + 6, 2);
		int a, r, g, b;
		a = 255 - strtol(sa, NULL, 16);
		r = strtol(sr, NULL, 16);
		g = strtol(sg, NULL, 16);
		b = strtol(sb, NULL, 16);
		palette[i] = gdImageColorAllocateAlpha(image, r, g, b, a);
		// std::cerr<< std::left<< std::setw(10)<< palette[i];
		// long value = (a << 24) + (r << 16) + (g << 8) + b;
		// palette[i] = *(int*)&value;
		// std::cerr << std::right << std::hex << std::setfill('0')
		//		<< std::setw(8) << palette[i] << "        ";
	}
	gdImageColorTransparent(image, palette[0]);
	return palette;
}

void MapRenderer_DumpImage(gdImagePtr image, std::ostream &output)
{
	ImageStreamOutput out(output);
	gdImagePngCtx(image, &out);
}

int ImageStreamOutput::Seek(struct gdIOCtx *io, const int offset)
{
	static_cast<ImageStreamOutput*>(io)->out.seekp(offset);
	return static_cast<ImageStreamOutput*>(io)->out.fail();
}

void ImageStreamOutput::Gd_free(struct gdIOCtx *io)
{
}

int ImageStreamOutput::GetBuf(struct gdIOCtx *op, void *buf, int len)
{
	return 0;
}

ImageStreamOutput::ImageStreamOutput(std::ostream & out) :out(out)
{
	this->getC = ImageStreamOutput::GetC;
	this->getBuf = ImageStreamOutput::GetBuf;
	this->putC = ImageStreamOutput::PutC;
	this->putBuf = ImageStreamOutput::PutBuf;
	this->seek = ImageStreamOutput::Seek;
	this->tell = ImageStreamOutput::Tell;
	this->gd_free = ImageStreamOutput::Gd_free;
}

int ImageStreamOutput::GetC(struct gdIOCtx *io)
{
	return -1;
}

long ImageStreamOutput::Tell(struct gdIOCtx *io)
{
	return static_cast<ImageStreamOutput*>(io)->out.tellp();
}

ImageStreamOutput::~ImageStreamOutput()
{
}

void ImageStreamOutput::PutC(struct gdIOCtx *io, int val)
{
	static_cast<ImageStreamOutput*>(io)->out.put((char)val);
}

int ImageStreamOutput::PutBuf(struct gdIOCtx *io, const void *buf,
		int len)
{
	static_cast<ImageStreamOutput*>(io)->out.write(static_cast<const char*>(buf), len);
	if (static_cast<ImageStreamOutput*>(io)->out.bad())
		return -1;
	return len;
}

